
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>


// 定义一个断言宏 (`__debugbreak();` 是MSVC的断言方法)
#define ASSERT(x) if (!(x)) __debugbreak();
// 定义一个GL指令宏: 在执行OpenGLAPI前调用GLClearError函数, 在执行OpenGLAPI后调用ASSERT(GLLogCall())函数
#define GLCALL(x) { GLClearError();\
	x;\
ASSERT(GLLogCall(#x, __FILE__, __LINE__)); }

static void GLClearError() {
    while (glGetError() != GL_NO_ERROR);
}

static bool GLLogCall(const char* functionName, const char* filePath, int line) {
    while (GLenum error = glGetError()) {
        std::cout << "[OpenGL Error]: (" << error << "): " << functionName << filePath << line << std::endl; // OpenGL的错误是16进制的, 这个错误码是10进制的
        return false;
    }
    return true;
}

// Shader程序代码模型
struct ShaderProgramSource {
    std::string VertexSource;
    std::string FragementSource;
};

// 封装函数从文件中读取shader, shader文件预处理函数
static ShaderProgramSource ParseShaderFile(const std::string& filePath) {
    enum class ShaderType {
        NONE = -1,
        VERTEX = 0,
        FRAGMENT = 1,
    };

    // 读取文件内容到line指针
    std::ifstream stream(filePath);

    std::string line;
    std::stringstream ss[2]; // 用于装载shader文件预处理内容模型
    ShaderType type = ShaderType::NONE;
    while (getline(stream, line)) {
        if (line.find("#shader") != std::string::npos) {
            if (line.find("vertex") != std::string::npos) { type = ShaderType::VERTEX; }
            else if (line.find("fragement") != std::string::npos) { type = ShaderType::FRAGMENT; }
        }

        // ! "#shader"
        else {
            ss[(int)type] << line << '\n';
        }
    }
    return { ss[0].str(), ss[1].str() };
}

// 编译shader程序
static unsigned int CompileShader(unsigned int type, const std::string& source) {
    unsigned int id = glCreateShader(type);
    const char* src = source.c_str();
    glShaderSource(id, 1, &src, nullptr); // https://docs.gl/gl4/glShaderSource
    glCompileShader(id);

    // TODO: Error Handling
    int result;
    glGetShaderiv(id, GL_COMPILE_STATUS, &result);
    if (result == GL_FALSE) {
        int length;
        glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
        char* message = (char*)alloca(length * sizeof(char)); // 在堆栈中申请一块内存用于从gl获取错误的编译信息
        glGetShaderInfoLog(id, length, &length, message);
        std::cout << "Failed to copmpile" << (type == GL_VERTEX_SHADER ? "vertex" : "fragment") << " shader" << std::endl;
        std::cout << message << std::endl;
        glDeleteShader(id);
        return 0;
    }

    return id;
}

// 创建shader程序
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader) {
    unsigned int program = glCreateProgram();
    unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertexShader);
    unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);

    glAttachShader(program, vs);
    glAttachShader(program, fs);
    glLinkProgram(program);
    glValidateProgram(program);

    // 对于shader是否需要清理
    // 1. shader程序体量很小, 不是很需要清理
    // 2. 如果删除了显卡的着色器程序, 那么着色器程序将无法进行调试
    glDeleteShader(vs);
    glDeleteShader(fs);

    return program;
}

int main(void) {

    // GLFW
    GLFWwindow* window;
    if (!glfwInit()) return -1; // Initializing GLFW
    // GLFW 配置
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL); // 初始化一个窗口并且获取它的OpenGL上下文
    if (!window) {
        glfwTerminate();
        return -1;
    }
    // GLFW 设置
    glfwMakeContextCurrent(window); // 将GLContext的指针指向当前创建的窗口
    glfwSwapInterval(1); // 与主显示器的刷新频率的比值

    // GLEW
    GLenum err = glewInit(); // https://glew.sourceforge.net/basic.html -> Initializing GLEW
    if (GLEW_OK != err) { std::cout << err << std::endl; }
    std::cout << glGetString(GL_VERSION) << std::endl; // 通过GLEW调用OpenGLAPI实现跨平台的目的

    // 准备需要渲染的数据
    unsigned int indcies[] = {
        0, 1, 2,
        2, 3, 0
    };
    float attr_posisitons[] = {
        -0.5f, -0.5f, // 0
         0.5f, -0.5f, // 1
         0.5f, 0.5f,  // 2
        -0.5f, 0.5f,  // 3
    };


    // VertexArrayObject
    unsigned int vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    // VertexAttributeBuffer
    unsigned int buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(float), attr_posisitons, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0); // active attributes buffer
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0); // 此次调用这个API会将buffer自动绑定到vao上

    // IndexBufferObject
    unsigned int ibo;
    glGenBuffers(1, &ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indcies, GL_STATIC_DRAW);

    // 预处理/编译/链接 处理Shader文件
    ShaderProgramSource source = ParseShaderFile("resources/shaders/HelloUniform.shader"); // properties -> DEBUG -> Working Directory VisualStudio默认不是main函数位置, 而是项目位置
    // 打印输出一下通过函数预处理之后的文件内容
    std::cout << "Vertex Shader Source: " << std::endl;
    std::cout << source.VertexSource << std::endl;
    std::cout << std::endl;
    std::cout << "Fragment Shader Source: " << std::endl;
    std::cout << source.FragementSource << std::endl;
    unsigned int shader = CreateShader(source.VertexSource, source.FragementSource);

    // Uniforms
    int uniformLocation = glGetUniformLocation(shader, "u_Color");
    ASSERT(uniformLocation != -1);

    // Render Loop
    float r = 0.0f;
    float increament = 0.05f;
    while (!glfwWindowShouldClose(window)) {
        glClear(GL_COLOR_BUFFER_BIT); // Clear buffers

        // 对于每个Object都需要做如下的操作
        {
            // Shader
            glUseProgram(shader);

            // Uniforms
            glUniform4f(uniformLocation, r, 0.0f, 0.0f, 1.0f);

            // // Attributes 这些都不需要了, 只需要在渲染前指定对应的vao即可
            // glBindBuffer(GL_ARRAY_BUFFER, buffer);
            // glEnableVertexAttribArray(0);
            // glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0);

            // 在渲染前指定对应的vao
            glBindVertexArray(vao);

            // // Indecies
            // glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

            // Render
            GLCALL(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr)); // https://docs.gl/gl4/glDrawElements
        }

        if (r > 1.0f) { increament = -0.05f; }
        else if (r < 0.0f) { increament = 0.05f; }
        r += increament;

        glfwSwapBuffers(window); // Swap front and back buffers
        glfwPollEvents(); // Poll for and process events
    }

    glDeleteProgram(shader);

    glfwTerminate();
    return 0;
}